﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Collections;
using Collections.TreeCollections;
using FeatureModelCore.FMTypes;
using System.Xml.Linq;
using System.IO;
using FeatureModelCore.FMShapes;
using FeatureModelCore.FMAnalyzer;
using System.Collections;
using FeatureModelCore.FMInterface;
using FeatureModelCore.Layout;
using FeatureModel.Collections.TreeArgs;
using FeatureModelCore.FeatureModelEventArgs;
using FeatureModelCore.FMCrossTreeConstraint;
using EventLogAssembly;
using FeatureModelCore.FMExceptions;
//using FeatureModelCore.FMCrossTreeConstraint;

namespace FeatureModelCore
{
    public class FeatureModelTree : Tree
    {
        #region Properties
        public FeatureTreeGroupRelationship[] AllGroupRelationships { get { return getAllGroupRelationships(); } }
        public IFeature[] AllFeatureNodes { get { return getAllTreeNodes(); } }
        //public FeatureTreeRelationship[] AllRelationships { get { return getAllRelationships(); } }

        public CrossTreeConstraint[] AllCrossTreeConstraints
        {
            get { return this._crossTreeConstraints.ToArray<CrossTreeConstraint>(); }
        }
        internal FeatureModelAnalyzer Analyzer { get { return _analyzer; } }
        #endregion

        #region Fields
        private FMSet<CrossTreeConstraint> _crossTreeConstraints;
        private FeatureModelAnalyzer _analyzer;
        #endregion

        #region Constructor
        /// <summary>
        /// Feature Model
        /// </summary>
        static FeatureModelTree()
        {
            TreeRegisterClass treeClass = new TreeRegisterClass(typeof(FeatureModelTree)
                , FeatureModelTree.ConstructFeatureModel
                , FeatureModelTree.ConstructFeatureModelRoot
                , FeatureModelTree.XmlParser);
            // feature tree node
            TreeObjectRegisterClass treeNode = new TreeObjectRegisterClass(
                typeof(FeatureTreeNode), FeatureTreeNode.ConstructTreeNode, FeatureTreeNode.xmlParser);
            treeClass.RegisterTreeObjectType(treeNode);

            // feature tree root
            TreeObjectRegisterClass treeRoot = new TreeObjectRegisterClass(
                typeof(FeatureTreeRoot), FeatureTreeRoot.ConstructRoot
                , FeatureTreeRoot.xmlParser);
            treeClass.RegisterTreeObjectType(treeRoot);

            // feature group relationship
            TreeObjectRegisterClass groupRelationship = new TreeObjectRegisterClass(
                typeof(FeatureTreeGroupRelationship), FeatureTreeGroupRelationship.ConstructGroupRelationship
                , FeatureTreeGroupRelationship.xmlParser);
            treeClass.RegisterTreeObjectType(groupRelationship);

            // feature relationship
            //TreeObjectRegisterClass relationship = new TreeObjectRegisterClass(
            //    typeof(FeatureTreeRelationship), FeatureTreeRelationship.ConstructRelationship
            //    , FeatureTreeRelationship.xmlParser);
            //treeClass.RegisterTreeObjectType(relationship);

            // feature property
            TreeObjectRegisterClass property = new TreeObjectRegisterClass(
                typeof(FeatureNodeProperty), FeatureNodeProperty.ConstructProperty
                , FeatureNodeProperty.xmlParser);
            treeClass.RegisterTreeObjectType(property);

            TreeRegisterTable.TreeClassRegisterTable.RegisterTreeClass(treeClass);
        }

        /// <summary>
        /// Feature Model
        /// </summary>
        public FeatureModelTree()
            : base()
        {
            _crossTreeConstraints = new FMSet<CrossTreeConstraint>();
            _analyzer = new FeatureModelAnalyzer(this);

            // add event handler
            this.TreeModifyCheck.AddHandler(this.checkExistedInstanceDeletion);
            this.TreeModifyCheck.AddHandler(this.validFeatureName);
            this.TreeModifyPrepare.AddHandler(this.prepareExistedInstanceDeletion);
        }

        public FeatureModelTree(FeatureTreeRoot root)
            : base(root)
        {
            _crossTreeConstraints = new FMSet<CrossTreeConstraint>();
            _analyzer = new FeatureModelAnalyzer(this);

            // add event handler
            this.TreeModifyCheck.AddHandler(this.checkExistedInstanceDeletion);
            this.TreeModifyCheck.AddHandler(this.validFeatureName);
            this.TreeModifyPrepare.AddHandler(this.prepareExistedInstanceDeletion);
        }
        #endregion

        #region Static Constructor
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static FeatureModelTree ConstructFeatureModel(object[] args)
        {
            return new FeatureModelTree();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="root"></param>
        /// <returns></returns>
        public static FeatureModelTree ConstructFeatureModelRoot(TreeRoot root, object[] args)
        {
            FeatureTreeRoot fRoot = root as FeatureTreeRoot;
            if (fRoot == null)
            {
                return null;
            }
            return new FeatureModelTree(fRoot);
        }
        #endregion

        #region Xml Parser
        /// <summary>
        /// To XDocument
        /// </summary>
        /// <returns></returns>
        public override XDocument ToXDoc()
        {
            return base.ToXDoc();
        }
        /// <summary>
        /// Parse XDocument to FeatureModel
        /// </summary>
        /// <param name="xdoc"></param>
        /// <returns></returns>
        public static FeatureModelTree XDocParser(XDocument xdoc)
        {
            FeatureModelTree fmTree = (FeatureModelTree)Collections.Tree.XDocParser(
                typeof(FeatureModelTree),xdoc);
            return fmTree;
        }
        /// <summary>
        /// To Xelem
        /// </summary>
        /// <returns></returns>
        public override XElement ToXElem()
        {
            XElement xelem = base.ToXElem();
            foreach (CrossTreeConstraint cons in _crossTreeConstraints)
            {
                xelem.Add(cons.ToXElem());
            }
            return xelem;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="xelem"></param>
        /// <param name="register"></param>
        /// <returns></returns>
        public static void XmlParser(XElement xelem, Tree tree)
        {
            FeatureModelTree fmTree = tree as FeatureModelTree;

            foreach (XElement xcons in xelem.Elements(typeof(CrossTreeConstraint).Name))
            {
                CrossTreeConstraint cons = CrossTreeConstraint.XmlParser(xcons);
                fmTree.CreateConstraint(cons.NodeIDA, cons.NodeIDB, cons.Type);
            }
        }
        #endregion

        #region Create Feature Object
        /// <summary>
        /// Create Feature Node
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public FeatureTreeNode CreateFeatureNode(FeatureTreeGroupRelationship parent
            , string featureName)
        {
            // check feature name
            // ValidFeatureName(featureName);

            TreeObject ret = this.CreateNewInstance(typeof(FeatureTreeNode), parent, featureName);
            FeatureTreeNode node = ret as FeatureTreeNode;
            if (node == null)
            {
                throw new Exception("Create Feature Tree Node Error!");
            }
            return node;
        }

        /// <summary>
        /// Create Group Relationship
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="gType"></param>
        /// <returns></returns>
        public FeatureTreeGroupRelationship CreateGroupRelationship(FeatureTreeRoot parent
            , FMTreeGroupRelationshipType gType)
        {
            TreeObject ret = this.CreateNewInstance(typeof(FeatureTreeGroupRelationship)
                , parent, gType);

            FeatureTreeGroupRelationship g = ret as FeatureTreeGroupRelationship;

            return g;
        }
        /// <summary>
        /// Create Group Relationship
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="gType"></param>
        /// <returns></returns>
        public FeatureTreeGroupRelationship CreateGroupRelationship(FeatureTreeNode parent
            , FMTreeGroupRelationshipType gType)
        {
            TreeObject ret = CreateNewInstance(typeof(FeatureTreeGroupRelationship)
    , parent, gType);

            FeatureTreeGroupRelationship g = ret as FeatureTreeGroupRelationship;

            return g;
        }
        /// <summary>
        /// Create Property in Feature Tree Node
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="attriName"></param>
        /// <returns></returns>
        public FeatureNodeProperty CreateProperty(FeatureTreeNode parent, string attriName)
        {
            TreeObject ret = this.CreateNewInstance(typeof(FeatureNodeProperty)
    , parent, attriName, FMPropertyType.User);
            FeatureNodeProperty property = ret as FeatureNodeProperty;

            return property;
        }
        /// <summary>
        /// Create Property in Feature Tree Root
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="attriName"></param>
        /// <returns></returns>
        public FeatureNodeProperty CreateProperty(FeatureTreeRoot parent, string attriName)
        {
            TreeObject ret = this.CreateNewInstance(typeof(FeatureNodeProperty)
    , parent, attriName, FMPropertyType.User);
            FeatureNodeProperty property = ret as FeatureNodeProperty;

            return property;
        }
        /// <summary>
        /// Create Cross Tree Constraint
        /// </summary>
        /// <param name="idA"></param>
        /// <param name="idB"></param>
        /// <param name="cType"></param>
        /// <returns></returns>
        public CrossTreeConstraint CreateConstraint(TreeNodeIdentify idA, TreeNodeIdentify idB
            , FMCrossTreeConstraintType cType)
        {
            TreeNodeIdentify id = this.GenerateId();
            string info = string.Format("Create cross tree constraint {0} {1} {2} in tree '{3}'"
                , idA, idB, cType.ToString(), this.GetType().Name);
            ConstraintCreatedEventArgs e = new ConstraintCreatedEventArgs(this, info, 0, id
                , idA, idB, cType);

            PreProcess(this, e);

            //this._analyzer.OptimizedTree.ValidCrossTreeConstraint(idA, idB, cType);
            
            CrossTreeConstraint cons = new CrossTreeConstraint(id, cType
                , idA, idB);
            // add cons in Cross Tree Constraint List
            _crossTreeConstraints.Add(cons);
            cons.Tree = this;

            setCrossTreeConstraintInfo(cons);
            //setCrossTreeConstraintInfo(cons);

            e.CreatedConstraint = cons;
            ProProcess(this, e);
            return cons;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="treeObj"></param>
        /// <param name="cons"></param>
        private void setCrossTreeConstraintInfo(CrossTreeConstraint cons)
        {
            TreeObject[] pathA = ((TreeObject)cons.NodeA).GetTreePath();
            TreeObject[] pathB = ((TreeObject)cons.NodeB).GetTreePath();

            // get the conjuction node
            CrossTreeConstraintInfoForNode info = null;
            int a = 0, b = 0;
            for (a = pathA.Length - 1, b = pathB.Length - 1;
                a >= 0 && b >= 0 && ReferenceEquals(pathA[a], pathB[b]); a--, b--)
            {
                IFeatureTreeObject fmObj = (IFeatureTreeObject)pathA[a];
                info = fmObj.ObjectExtension.addConstraint(cons, cons.NodeA, cons.NodeB, CrossTreeConstraintInfoType.Inner);
            }
            info.InfoType = CrossTreeConstraintInfoType.Current;

            for (; a >= 0; a--)
            {
                IFeatureTreeObject fmObj = (IFeatureTreeObject)pathA[a];
                info = fmObj.ObjectExtension.addConstraint(cons, cons.NodeA, null, CrossTreeConstraintInfoType.Outer);
            }

            for (; b >= 0; b--)
            {
                IFeatureTreeObject fmObj = (IFeatureTreeObject)pathB[b];
                info = fmObj.ObjectExtension.addConstraint(cons, null, cons.NodeB, CrossTreeConstraintInfoType.Outer);
            }
        }
        #endregion

        #region Delete
        /// <summary>
        /// 
        /// </summary>
        /// <param name="treeObj"></param>
        private void checkExistedInstanceDeletion(object sender, TreeEventArgs e)
        {
            TreeObjectDeletedEventArgs args = e as TreeObjectDeletedEventArgs;
            if (args == null)
            {
                return;
            }

            IFeatureTreeObject modelObj = (IFeatureTreeObject)args.ObjectToDelete;
            foreach (CrossTreeConstraintInfoForNode consInfo in modelObj.ObjectExtension.OuterConstraint)
            {
                if (consInfo.CrossTreeConstraint.Type == FMCrossTreeConstraintType.Require
                    && consInfo.NodeB != null)
                {
                    throw new ExistedInstanceDeletionException(sender, args
                        , "The tree node is required by other node.");
                }
            }
        }
        /// <summary>
        /// Prepare to delete existed deletion.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void prepareExistedInstanceDeletion(object sender, TreeEventArgs e)
        {
            TreeObjectDeletedEventArgs args = e as TreeObjectDeletedEventArgs;
            if (args == null)
            {
                return;
            }

            IFeature obj = (IFeature)args.ObjectToDelete;
            foreach (CrossTreeConstraintInfoForNode consInfo in obj.ObjectExtension.OuterConstraint)
            {
                this.DeleteCrossTreeConstraint((CrossTreeConstraint)consInfo.CrossTreeConstraint);
            }
        }
        /// <summary>
        /// Delete cross tree constaint
        /// </summary>
        /// <param name="constraint"></param>
        public void DeleteCrossTreeConstraint(CrossTreeConstraint constraint)
        {
            string info = string.Format("Delete cross tree constraint '{0}' from tree '{1}'"
                , constraint.ToString(), this.GetType().Name);
            ConstraintDeleteEventArgs e = new ConstraintDeleteEventArgs(this, info, 0
                , constraint);
            PreProcess(this, e);
            _crossTreeConstraints.Remove(constraint);

            removeConsInfo(constraint);
            //removeConsInfo((TreeObject)constraint.NodeB, constraint);

            ProProcess(this, e);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="cons"></param>
        private void removeConsInfo(CrossTreeConstraint cons)
        {
            TreeObject[] pathA = ((TreeObject)cons.NodeA).GetTreePath();
            TreeObject[] pathB = ((TreeObject)cons.NodeB).GetTreePath();

            foreach (IFeatureTreeObject treeObj in pathA)
            {
                treeObj.ObjectExtension.removeConstaint(cons.ID);
            }
            foreach (IFeatureTreeObject treeObj in pathB)
            {
                treeObj.ObjectExtension.removeConstaint(cons.ID);
            }
        }
        #endregion

        #region Equals
        /// <summary>
        /// Equals
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool TotallyEquals(Tree tree)
        {
            FeatureModelTree fmTree = tree as FeatureModelTree;
            if (fmTree == null)
            {
                return false;
            }
            bool flag = base.TotallyEquals(tree);
            if (flag == false)
            {
                return false;
            }
            flag = this._crossTreeConstraints.Equals(
                fmTree._crossTreeConstraints);
            return flag;
        }
        /// <summary>
        /// Tree Hierarchy 
        /// </summary>
        /// <param name="tree"></param>
        /// <returns></returns>
        public override bool TreeHierarchyEquals(Tree tree)
        {
            FeatureModelTree fmTree = tree as FeatureModelTree;
            if (fmTree == null)
            {
                return false;
            }
            bool flag = base.TreeHierarchyEquals(tree);
            if (flag == false)
            {
                return false;
            }
            flag = this._crossTreeConstraints.Equals(
                fmTree._crossTreeConstraints);
            return flag;
        }
        #endregion

        #region Save & Load
        /// <summary>
        /// Save
        /// </summary>
        public void Save()
        {
            if (this.HasPathInfo == false)
            {
                throw new Exception("No File Path");
            }
            string filePath = this.FullFilePath;
            SaveAs(filePath);
        }
        /// <summary>
        /// Save as
        /// </summary>
        /// <param name="filePath"></param>
        public void SaveAs(string filePath)
        {
            XDocument xdoc = this.ToXDoc();
            using (StreamWriter writer = File.CreateText(filePath))
            {
                writer.Write(xdoc);
            }
        }
        /// <summary>
        /// Load
        /// </summary>
        /// <param name="fileFullPath"></param>
        /// <returns></returns>
        public static FeatureModelTree Load(string fileFullPath)
        {
            string file = string.Empty;
            using (StreamReader reader = File.OpenText(fileFullPath))
            {
                file = reader.ReadToEnd();
            }
            bool isEmpty = false;
            if (file == null)
            {
                isEmpty = true;
            }
            file = file.Trim();
            if (file == string.Empty)
            {
                isEmpty = true;
            }

            FeatureModelTree tree = null;
            if (isEmpty)
            {
                tree = new FeatureModelTree();
                FileInfo fileInfo = new FileInfo(fileFullPath);
                ((FeatureTreeRoot)tree.Root).FeatureName = fileInfo.Name;
                tree.SetFileInfo(fileFullPath);
                tree.Save();

                return tree;
            }

            XDocument xdoc = XDocument.Parse(file);
            tree = (FeatureModelTree)Tree.XDocParser(typeof(FeatureModelTree), xdoc);
            tree.SetFileInfo(fileFullPath);

            // check tree graphic information
            bool needToSetLayout = NeedToSetLayout(tree);
            if (needToSetLayout)
            {
                TreeLayout.SetFeatureModelLayout(tree);
            }

            tree._dirty = false; // just for load
            return tree;
        }
        #endregion

        public override object Clone()
        {
            XElement xelem = this.ToXElem();
            return Tree.XElemParser(xelem);
        }

        #region Shape

        /// <summary>
        /// Need to set layouts
        /// </summary>
        /// <param name="fmTree"></param>
        /// <returns></returns>
        public static bool NeedToSetLayout(FeatureModelTree fmTree)
        {
            FeatureTreeRoot root = (FeatureTreeRoot)fmTree.Root;
            if (root.Shape.Width == -1
                || root.Shape.Height == -1
                || root.Shape.Center.X == -1
                || root.Shape.Center.Y == -1)
            {
                return true;
            }

            foreach (IFeature node in fmTree.AllFeatureNodes)
            {
                if (node.Shape.Width == -1
                    || node.Shape.Height == -1
                    || node.Shape.Center.X == -1
                    || node.Shape.Center.Y == -1)
                {
                    return true;
                }
            }

            foreach (FeatureTreeGroupRelationship gR in fmTree.AllGroupRelationships)
            {
                if (gR.Shape.Radius == -1
                    || gR.Shape.Center.X == -1
                    || gR.Shape.Center.Y == -1)
                {
                    return true;
                }
            }

            return false;
        }
        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="layList"></param>
        /// <param name="pestLay"></param>
        private static void subGetTreeNodeLayout(List<List<IFeature>> layList
            , List<IFeature> pestLay)
        {
            List<IFeature> thisLay = new List<IFeature>();
            foreach (IFeature node in pestLay)
            {
                thisLay.AddRange(node.ChildrenNodes);
            }
            if (thisLay.Count == 0)
            {
                return;
            }

            layList.Add(thisLay);
            subGetTreeNodeLayout(layList, thisLay);

        }


        #region Valid
        /// <summary>
        /// Check whether the name already existed.
        /// </summary>
        private void validFeatureName(object sender, TreeEventArgs e)
        {
            FeatureNameChangedEventArgs args = e as FeatureNameChangedEventArgs;
            if (args == null)
            {
                return;
            }

            // valid tree root
            FeatureTreeRoot root = (FeatureTreeRoot)this.Root;

            foreach (IFeature node in AllFeatureNodes)
            {
                if (node.FeatureName == args.NewName)
                {
                    throw new Exception(
                        string.Format("Feature Node Name:{0} exists."
                        , args.NewName));
                }
            }
        }
        #endregion

        #region Get Tree Object
        /// <summary>
        /// Get all tree nodes
        /// </summary>
        /// <returns></returns>
        private IFeature[] getAllTreeNodes()
        {
            // get all nodes
            List<IFeature> nodes = new List<IFeature>();

            foreach (TreeObject obj in this._objStorage)
            {
                if (obj is IFeature)
                {
                    nodes.Add(((IFeature)obj));
                }
            }

            return nodes.ToArray<IFeature>();
        }
        /// <summary>
        /// Get all tree group relationship
        /// </summary>
        /// <returns></returns>
        private FeatureTreeGroupRelationship[] getAllGroupRelationships()
        {
            // all group relationships
            List<FeatureTreeGroupRelationship> gRelationships
                = new List<FeatureTreeGroupRelationship>();

            foreach (TreeObject obj in this._objStorage)
            {
                if (obj is FeatureTreeGroupRelationship)
                {
                    gRelationships.Add(((FeatureTreeGroupRelationship)obj));
                }
            }

            return gRelationships.ToArray<FeatureTreeGroupRelationship>();
        }
        ///// <summary>
        ///// Get all tree relationship
        ///// </summary>
        ///// <returns></returns>
        //private FeatureTreeRelationship[] getAllRelationships()
        //{
        //    // all relationships
        //    List<FeatureTreeRelationship> relationships
        //        = new List<FeatureTreeRelationship>();

        //    foreach (TreeObject obj in this._objStorage)
        //    {
        //        if (obj is FeatureTreeRelationship)
        //        {
        //            relationships.Add(((FeatureTreeRelationship)obj));
        //        }
        //    }

        //    return relationships.ToArray<FeatureTreeRelationship>();
        //}
        /// <summary>
        /// Get Feature Node Based on feature name
        /// </summary>
        /// <param name="featureName"></param>
        /// <returns></returns>
        public IFeature GetFeatureNode(string featureName)
        {
            foreach (TreeObject obj in this._objStorage)
            {
                IFeature node = obj as IFeature;
                if (node == null)
                {
                    continue;
                }
                if (node.FeatureName == featureName)
                {
                    return node;
                }
            }
            return null;
        }
        /// <summary>
        /// Get Cross Tree Constraint
        /// </summary>
        /// <param name="nodeAId"></param>
        /// <param name="nodeBId"></param>
        /// <returns></returns>
        public CrossTreeConstraint GetCrossTreeConstraint(string id)
        {
            foreach (CrossTreeConstraint cons in this._crossTreeConstraints)
            {
                if (cons.ID.ToString() == id)
                {
                    return cons;
                }
            }
            return null;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public CrossTreeConstraint GetCrossTreeConstraint(TreeNodeIdentify id)
        {
            return GetCrossTreeConstraint(id.ToString());
        }
        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public FMProductCollection AllProducts()
        {
            return _analyzer.AllProducts();
        }
    }
}
